// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;

namespace System.Security.Cryptography
{
    /// <summary>
    ///   Represents a specific algorithm within the ML-DSA family.
    /// </summary>
    [DebuggerDisplay("{Name,nq}")]
    public sealed class MLDsaAlgorithm : IEquatable<MLDsaAlgorithm>
    {
        /// <summary>
        ///   Gets the underlying string representation of the algorithm name.
        /// </summary>
        /// <value>
        ///   The underlying string representation of the algorithm name.
        /// </value>
        public string Name { get; }

        /// <summary>
        ///   Gets the size, in bytes, of the ML-DSA private key for the current ML-DSA algorithm.
        /// </summary>
        /// <value>
        ///   The size, in bytes, of the ML-DSA private key for the current ML-DSA algorithm.
        /// </value>
        public int PrivateKeySizeInBytes { get; }

        /// <summary>
        ///   Gets the size, in bytes, of the ML-DSA private seed for the current ML-DSA algorithm.
        /// </summary>
        /// <value>
        ///   The size, in bytes, of the ML-DSA private seed for the current ML-DSA algorithm.
        /// </value>
        public int PrivateSeedSizeInBytes => 32;

        /// <summary>
        ///   Gets the size of the ML-DSA public key for the current ML-DSA algorithm.
        /// </summary>
        /// <value>
        ///   The size, in bytes, of the ML-DSA public key for the current ML-DSA algorithm.
        /// </value>
        public int PublicKeySizeInBytes { get; }

        /// <summary>
        ///   Gets the size, in bytes, of the signature for the current ML-DSA algorithm.
        /// </summary>
        /// <value>
        ///   The size, in bytes, of the signature for the current ML-DSA algorithm.
        /// </value>
        public int SignatureSizeInBytes { get; }

        /// <summary>
        ///   Gets the size, in bytes, of the mu (&#x3BC;) value for the current ML-DSA algorithm.
        /// </summary>
        /// <value>
        ///   The size, in bytes, of the mu (&#x3BC;) value for the current ML-DSA algorithm.
        /// </value>
        public int MuSizeInBytes => 64;

        internal string Oid { get; }
        internal int LambdaCollisionStrength { get; }

        private MLDsaAlgorithm(
            string name,
            int privateKeySizeInBytes,
            int publicKeySizeInBytes,
            int signatureSizeInBytes,
            int lambdaCollisionStrength,
            string oid)
        {
            Name = name;
            PrivateKeySizeInBytes = privateKeySizeInBytes;
            PublicKeySizeInBytes = publicKeySizeInBytes;
            SignatureSizeInBytes = signatureSizeInBytes;
            LambdaCollisionStrength = lambdaCollisionStrength;
            Oid = oid;
        }

        // TODO: Our algorithm names generally match CNG.  If they don't in this case, consider changing the values.
        // TODO: These values match OpenSSL names, if changing this for CNG, we should make sure to do the right thing for OpenSSL.

        /// <summary>
        ///   Gets an ML-DSA algorithm identifier for the ML-DSA-44 algorithm.
        /// </summary>
        /// <value>
        ///   An ML-DSA algorithm identifier for the ML-DSA-44 algorithm.
        /// </value>
        public static MLDsaAlgorithm MLDsa44 { get; } = new MLDsaAlgorithm("ML-DSA-44", 2560, 1312, 2420, 128, Oids.MLDsa44);

        /// <summary>
        ///   Gets an ML-DSA algorithm identifier for the ML-DSA-65 algorithm.
        /// </summary>
        /// <value>
        ///   An ML-DSA algorithm identifier for the ML-DSA-65 algorithm.
        /// </value>
        public static MLDsaAlgorithm MLDsa65 { get; } = new MLDsaAlgorithm("ML-DSA-65", 4032, 1952, 3309, 192, Oids.MLDsa65);

        /// <summary>
        ///   Gets an ML-DSA algorithm identifier for the ML-DSA-87 algorithm.
        /// </summary>
        /// <value>
        ///   An ML-DSA algorithm identifier for the ML-DSA-87 algorithm.
        /// </value>
        public static MLDsaAlgorithm MLDsa87 { get; } = new MLDsaAlgorithm("ML-DSA-87", 4896, 2592, 4627, 256, Oids.MLDsa87);

        internal static MLDsaAlgorithm? GetMLDsaAlgorithmFromOid(string? oid)
        {
            return oid switch
            {
                Oids.MLDsa44 => MLDsa44,
                Oids.MLDsa65 => MLDsa65,
                Oids.MLDsa87 => MLDsa87,
                _ => null,
            };
        }

        /// <summary>
        ///   Compares two <see cref="MLDsaAlgorithm" /> objects.
        /// </summary>
        /// <param name="other">
        ///   An object to be compared to the current <see cref="MLDsaAlgorithm"/> object.
        /// </param>
        /// <returns>
        ///   <see langword="true" /> if the objects are considered equal; otherwise, <see langword="false" />.
        /// </returns>
        // This is a closed type, so all we need to compare are the names.
        public bool Equals([NotNullWhen(true)] MLDsaAlgorithm? other) => other is not null && other.Name == Name;

        /// <inheritdoc />
        public override bool Equals([NotNullWhen(true)] object? obj) => obj is MLDsaAlgorithm alg && alg.Name == Name;

        /// <inheritdoc />
        public override int GetHashCode() => Name.GetHashCode();

        /// <inheritdoc />
        public override string ToString() => Name;

        /// <summary>
        ///   Determines whether two <see cref="MLDsaAlgorithm" /> objects specify the same algorithm name.
        /// </summary>
        /// <param name="left">
        ///   An object that specifies an algorithm name.
        /// </param>
        /// <param name="right">
        ///   A second object, to be compared to the object that is identified by the <paramref name="left" /> parameter.
        /// </param>
        /// <returns>
        ///   <see langword="true" /> if the objects are considered equal; otherwise, <see langword="false" />.
        /// </returns>
        public static bool operator ==(MLDsaAlgorithm? left, MLDsaAlgorithm? right)
        {
            return left is null ? right is null : left.Equals(right);
        }

        /// <summary>
        ///   Determines whether two <see cref="MLDsaAlgorithm" /> objects do not specify the same algorithm name.
        /// </summary>
        /// <param name="left">
        ///   An object that specifies an algorithm name.
        /// </param>
        /// <param name="right">
        ///   A second object, to be compared to the object that is identified by the <paramref name="left" /> parameter.
        /// </param>
        /// <returns>
        ///   <see langword="true" /> if the objects are not considered equal; otherwise, <see langword="false" />.
        /// </returns>
        public static bool operator !=(MLDsaAlgorithm? left, MLDsaAlgorithm? right)
        {
            return !(left == right);
        }
    }
}
